#include "buffer.h"

Buffer * Buffer::instance = NULL;
		
Buffer::Buffer(Buffer &b){
		
}

void Buffer::map_buffer(){
    std::list<Nodo *>::iterator it = this->lista_bloques.begin();
    Nodo* nodo = NULL;
    std::cout << "==================INICIO=========================\n";
    std::cout << "Espacio libre: ";
    std::cout << this->espacio_libre;
    std::cout << "\n\n";
    while ( it != lista_bloques.end() ) {
        nodo = *it;
        if( nodo->valido ){
            std::cout << "--Archivo: ";
            std::cout << nodo->archivo->get_nombre();
            std::cout << " - Numero bloque: ";
            std::cout << nodo->num_bloque;
            if(nodo->modificado)
                std::cout << " M  ";
            std::cout << " V\n";
            std::cout << std::flush;
        }
        else{
       		std::cout << "--Archivo: XXXXXXXX";
            std::cout << " - Numero bloque: ";
            std::cout << nodo->num_bloque;
            if(nodo->modificado)
                std::cout << " M  ";
            std::cout << " NV\n";    
            std::cout << std::flush;
        }	
        
        
        ++it;   
    }
    std::cout << "==================FIN==============================\n";
   
}
		
Buffer::Buffer(unsigned int tam){
	
	//aca es donde se crea donde se almacena el buffer.
	buf = new char[tam];
	size = tam;
	for (unsigned int cont = 0; cont < this->size; cont++)
		this->buf[cont] = 0;
	cant_validos = 0;
	espacio_libre = tam;
		
}
	
Buffer * Buffer::get_instance(unsigned int tam){
	if( Buffer::instance == NULL )
		Buffer::instance = new Buffer(tam);
	return Buffer::instance;
}

Buffer::~Buffer(){
	std::list<Nodo *>::iterator it = this->lista_bloques.begin();
	Nodo* nodo = NULL;
	while ( it != lista_bloques.end() ) {
		nodo = *it;
		if ((nodo->valido) && (nodo->modificado)) {
			ManejadorArchivos::get_instance()->escribir_bloque(nodo->archivo->get_fd(), nodo->num_bloque, nodo->posicion, nodo->tam_ocupado);	
		}
		delete (*it);
		++it;	
	} 
	delete[] buf;
}

Buffer::Nodo * Buffer::buscar_bloque(Archivo &arch, unsigned int num_bloque){
	Nodo * retorno = NULL;
	Nodo * nodo;
	
	std::list<Nodo *>::iterator it = this->lista_bloques.begin();
	std::stringstream ss;
	std::string msj;
	
	while ( (retorno == NULL) && (it != lista_bloques.end()) ){
		nodo = *it;
		if( (nodo->valido) && (*(nodo->archivo) == arch) && ( nodo->num_bloque == num_bloque ) ){
			//lo saco de donde estaba
			retorno = nodo;
			msj= generar_encabezado();
			msj.append("Se encuentra bloque ");
			ss.str("");
			ss<<num_bloque;
			msj+=ss.str();
			msj.append(" del archivo ");
			msj+=arch.get_nombre();
			msj.append(" en buffer, es el ultimo usado.");
			(Debug::get_instance() )->mostrar_mensaje(msj);
			lista_bloques.erase(it);
			//lo pongo al principio como mas usado
			lista_bloques.push_front(retorno);
		}
		else
			it++;
	}
	return retorno;
}

void Buffer::compactar(Nodo* eliminado) {
	unsigned int despl = eliminado->posicion - buf;
	unsigned int cant = size - despl - eliminado->tam_ocupado;
	if( cant != 0 ){
		memcpy(eliminado->posicion, eliminado->posicion + eliminado->tam_ocupado, cant);
		std::list<Nodo *>::iterator it;
		Nodo* aux;
		for (it = lista_bloques.begin(); it != lista_bloques.end(); ++it){
			aux = *it;
			if (aux->posicion > eliminado->posicion)
				aux->posicion -= eliminado->tam_ocupado;
		}
	}
}

void Buffer::liberar_bloque_LRU(){
	if( lista_bloques.size() != 0 ){		
		Nodo* LRU = this->lista_bloques.back();
		if( (LRU->valido) && (LRU->modificado) ){
			ManejadorArchivos::get_instance()->escribir_bloque(LRU->archivo->get_fd(),LRU->num_bloque,LRU->posicion,LRU->tam_ocupado);	
		}
		if(LRU->valido) 
			cant_validos--;
		this->compactar(LRU);
		this->espacio_libre += LRU->tam_ocupado;
		this->lista_bloques.pop_back();
		std::stringstream ss;
		ss << LRU->num_bloque;
		delete LRU;
		LRU = NULL;
		std::string msj = generar_encabezado();
		msj.append("Se libero bloque LRU. Bloque: ");
		msj += ss.str();
		(Debug::get_instance() )->mostrar_mensaje(msj);
	}
		
	
}

void Buffer::liberar_espacio(unsigned int tam){
	bool entra = (tam <= this->espacio_libre);
	while (!entra) {
		liberar_bloque_LRU();
		entra = (tam <= this->espacio_libre);
	}	
}	

bool Buffer::alocar_bloque(Archivo &arch, Bloque * bloque, bool modif){
	std::string msj;
	std::stringstream ss;		
	if( this->size < arch.get_tam_bloque() ){
		msj = generar_encabezado();
		msj.append("El bq del arch ");
		msj+= arch.get_nombre();
		msj.append(" con tam de bq ");
		ss<< arch.get_tam_bloque();
		msj += ss.str();
		msj.append(" no se pudo alocar,se trabaja desde disco");
		(Debug::get_instance() )->mostrar_mensaje(msj);	
		return false;
	}
	liberar_espacio(arch.get_tam_bloque());
	//creo un nodo
	Nodo * nodo = new Nodo();
	//guardo el archivo que deseo
	nodo->archivo = &arch;
	//le asigno el numero de bloque al cual corresponde el nodo
	nodo->num_bloque = bloque->get_num_bloque();
	//lo setteo como valido
	nodo->valido = true;
	this->cant_validos++;
	//le pongo que el tam que ocupa es el del tam del registro del archivo
	nodo->tam_ocupado = arch.get_tam_bloque();
	//lo setteo como modif (por param)
	nodo->modificado = modif;
	//calculo la posicion, que es al final del buffer
	nodo->posicion = buf + (size - espacio_libre);
	//creo un vector donde guardo el contenido del bloque
	char * cont;
	cont = bloque->get_contenido();
	//lo paso al buffer
	memcpy(nodo->posicion ,cont, nodo->tam_ocupado);
	//libero el vector temporal
	delete[] cont;
	//achico la cantidad de espacio libre
	this->espacio_libre -= nodo->tam_ocupado;
	//meto el nodo en la lista
	lista_bloques.push_front(nodo);
	msj = generar_encabezado();
	msj.append("Se aloco bloque ");
	ss<<bloque->get_num_bloque();
	msj += ss.str();
	msj.append(" en buffer.");
	(Debug::get_instance() )->mostrar_mensaje(msj);
	return true;
}
		
Bloque * Buffer::get_bloque(Archivo &archivo, unsigned int num_bloque) throw (ExcepLeerBloque){
	Nodo * nodo;
	Bloque * bloque_retorno = NULL;
	//busco el nodo
	nodo = buscar_bloque(archivo, num_bloque);
	if( nodo != NULL ){
		//como existe, creo un bloque y le escribo el contenido del nodo
		bloque_retorno = new Bloque(num_bloque,nodo->tam_ocupado);
		bloque_retorno->set_contenido(nodo->posicion,nodo->tam_ocupado);
	}
	else{
		std::stringstream ss;
		std::string msj = generar_encabezado();
		msj.append("El bloque ");
		ss<< num_bloque;
		msj+=ss.str();
		msj.append(" del archivo ");
		msj+=archivo.get_nombre();
		msj.append(" no esta en el buffer se busca en disco.");
		(Debug::get_instance() )->mostrar_mensaje(msj);
		//como no existe, lo leo de disco
		char * bloque_leido = ManejadorArchivos::get_instance()->leer_bloque(archivo.get_fd(),num_bloque,archivo.get_tam_bloque());		
		//creo un bloque para devolver
		bloque_retorno = new Bloque( num_bloque,archivo.get_tam_bloque());
		//le copio lo que lei de disco
		bloque_retorno->set_contenido(bloque_leido, archivo.get_tam_bloque() );
		//lo aloco
		alocar_bloque(archivo, bloque_retorno);
		//borro el temporal de la lectura de disco
		delete[] bloque_leido;
	}
	return bloque_retorno;	
	
}

void Buffer::guardar_bloque(Archivo &archivo, Bloque * bloque)  throw (ExcepEscribirBloque){
	Nodo * nodo;
	nodo = buscar_bloque(archivo, bloque->get_num_bloque() );
	std::stringstream ss;
	std::string msj = generar_encabezado();
	if( nodo != NULL ){
		if(nodo->valido){
			msj.append("Se coloca el bloque ");
			ss<<bloque->get_num_bloque();
			msj+=ss.str();
			msj.append(" del archivo ");
			msj+=archivo.get_nombre();
			msj.append(" modificado en el buffer.");
			(Debug::get_instance() )->mostrar_mensaje(msj);
			//lo setteo como modificado
			nodo->modificado = true;
			//creo un vector auxiliar para leer del bloque
			char * contenido = bloque->get_contenido();
			//lo copio en el buffer
			memcpy( nodo->posicion, contenido , nodo->tam_ocupado );
			delete[] contenido;
		}
	}
	else{
		msj.append("El bq ");
		ss<<bloque->get_num_bloque();
		msj+=ss.str();
		msj.append(" del archivo ");
		msj+=archivo.get_nombre();
		msj.append(" no esta en el buffer, se escribe.");
		(Debug::get_instance() )->mostrar_mensaje(msj);
		//trato de alocar, si no pudo, lo escribo directamente en disco
		if( !alocar_bloque(archivo,bloque, true) ){
			//creo un vector para leer el contenido del bloque
			char * contenido = bloque->get_contenido();
			//lo paso a disco
			ManejadorArchivos::get_instance()->escribir_bloque(archivo.get_fd(),bloque->get_num_bloque(),contenido,bloque->get_size());		
			//libero la memoria ocupada por el vector
			delete[] contenido;
		}
	}
}
void Buffer::invalidar_bloque(Archivo &arch, unsigned int num_bloque){
	Nodo * nodo = buscar_bloque(arch, num_bloque);
	if( nodo != NULL ){
		this->cant_validos--;
		nodo->valido = false;
	}
}

		 						
std::string Buffer::generar_encabezado(){
	std::string cadena = "<BUFFER -Tam libre:";
	std::stringstream ss;
	ss << (this->espacio_libre);
	cadena += ss.str();
	ss.str("");
	cadena.append(" -Cant.Alocados:");
	ss << (this->lista_bloques.size());
	cadena += ss.str();
	ss.str("");	
	cadena.append(" -Cant.Validos:");
	ss << (this->cant_validos);
	cadena += ss.str();
	cadena.append("> ");
	return cadena;
}

		 													
void Buffer::guardar_en_disco (Archivo* arch) {
	list<Nodo*>::iterator it = this->lista_bloques.begin();
	Nodo* nodo;
	unsigned int cont =0;
	for (it = this->lista_bloques.begin(); it != lista_bloques.end(); ++it) {
		nodo = (*it);
		if( nodo->valido &&  (*(nodo->archivo) == *arch) ){
			if((nodo->modificado) )
				ManejadorArchivos::get_instance()->escribir_bloque(nodo->archivo->get_fd(), nodo->num_bloque, nodo->posicion, nodo->tam_ocupado);	
			nodo->valido = false;
			this->cant_validos--;
			cont++;
		}
	}
	std::stringstream ss;
	std::string msj = generar_encabezado();
	msj+= "Se cerro el archivo "+arch->get_nombre()+" .Se liberaron ";
	ss<<cont;
	msj+=ss.str();
	msj.append(" bloques en el buffer.");
	(Debug::get_instance() )->mostrar_mensaje(msj);	
}	


 		
void Buffer::destroy(){
	delete (Buffer::instance);	
}

void Buffer::actualizar(Observable * observable, void * param) {
	Archivo* arch = (Archivo*) observable;	
	guardar_en_disco(arch);		
}
